#! /usr/bin/python
# -*- coding: utf-8 -*-
# @author HanYZ
# @createAt 2022.07.28
# 网易萧先生-出水芙蓉

import backtrader as bt

class StrategyClass(bt.Strategy):
    '''出水芙蓉'''

    params = dict(
        topdistence = 55,        # 山顶距离不小于此值
        bottomdistence = 10,     # 底部距离不大于此值
        topvbottom = 1.3,        # 底x此值不大于顶
        currentvbottom = 1.05,  # 底x此值不大于当前收盘
        greenbodyp = 100,       # 阴线实体x此值要大于收盘价, 0 代表不控制
        holdperiod = 10,        # 计划持股周期
        bigvmean = 1.81,        # 前期柱高均值x此值要大于近期柱高
        code = None,
        name = None,
        log = True,
    )

    def log(self, txt, dt=None, force=False):
        if force or self.p.log:
            dt = dt or self.data.datetime.datetime()
            who = f'{self.p.code} {self.p.name} ' if self.p.code else ''
            print(f'{dt.isoformat()} {who}{txt}')

    def notify_order(self, order):
        if order.status in [bt.Order.Submitted, bt.Order.Accepted]:
            return

        if order.status == order.Completed:
            if order.isbuy():
                buytxt = '买入价:%.2f, 量:%s, 持仓:%s' % (order.executed.price, order.executed.size, self.position.size)
                self.log(buytxt, bt.num2date(order.executed.dt))
            else:
                selltxt = '卖出价, %.2f, 量:%s, 持仓:%s' % (
                    order.executed.price, order.executed.size, self.position.size)
                if 'log' in order.info:
                    selltxt = '%s %s' % (selltxt, order.info.log)
                self.log(selltxt, bt.num2date(order.executed.dt))

        elif order.status in [order.Expired, order.Canceled, order.Margin]:
            if 'log' not in order.info:
                self.log('%s , %s' % (order.Status[order.status], order))
            pass

    def __init__(self):
        red = self.data.close > self.data.open
        green = self.data.close < self.data.open

        topA = bt.ind.Highest(self.data.high, period=self.p.topdistence)
        topB = bt.ind.Highest(self.data.high, period=self.p.bottomdistence)
        bottomA = bt.ind.Lowest(self.data.low, period=self.p.topdistence)
        bottomB = bt.ind.Lowest(self.data.low, period=self.p.bottomdistence)

        body_high = bt.If(red, self.data.close, self.data.open)
        body_low = bt.If(green, self.data.close, self.data.open)
        body = body_high - body_low

        body_mean = bt.ind.SMA(body, period=self.p.topdistence)
        body_highest = bt.ind.Highest(body, period=self.p.bottomdistence)

        premise = bt.And(
            topA > bottomA * self.p.topvbottom,         # 跌幅
            bottomA == bottomB, topB > topB(-1),        # 止跌
            bottomA * self.p.currentvbottom < topB,     # 上涨不多
            body_highest < body_mean * self.p.bigvmean, # 近期木有大柱（上涨缓慢）
        )
        if self.p.greenbodyp > 0:
            condition = bt.And(
                green(-1), red,
                body(-1) * self.p.greenbodyp > self.data.close,
                self.data.high > self.data.high(-1),
                self.data.low > self.data.low(-1),
                self.data.close > self.data.open,
                self.data.low(-1) > bottomA,
            )
        else:
            condition = bt.And(
                green(-1), red,
                self.data.high > self.data.high(-1),
                self.data.low > self.data.low(-1),
                self.data.close > self.data.open,
                self.data.low(-1) > bottomA,
            )

        # 大柱
        self.bigbody = bt.Or(body > body_highest(-1), body > body_mean(-1) * self.p.bigvmean)
        # 最低价
        self.stopprice = bt.ind.Lowest(self.data.low, period=2)

        signal_buy = bt.And(premise, condition)
        signal_sell = bt.Or(bt.And(self.bigbody, red), green)
        self.signal = bt.If(signal_buy, 1, bt.If(signal_sell, -1, 0))

        self.waitfor = None

    def next(self):
        if self.waitfor:
            stop = self.waitfor[1]
            if stop.status in [bt.Order.Submitted, bt.Order.Accepted]:
                if len(self) == self.waitfor[0]:
                    self.sell(exectype=bt.Order.Close, log='按计划结单', oco=stop)
                elif self.signal[0] < 0:
                    log = '阴线止盈' if self.data.open[0] > self.data.close[0] else '大阳止盈'
                    self.sell(exectype=bt.Order.Market, log=log, oco=self.waitfor[1])
                else:
                    return
            self.waitfor = None
        elif self.signal[0] > 0:
            self.buy()
            stop = self.sell(exectype=bt.Order.Stop, price=self.stopprice, log=' 初始止损')
            self.waitfor = [len(self)+9, stop]

EasyNetLotus01 = StrategyClass
